#ifndef PORT_COMM_
#define PORT_COMM_

#include "include/port_comm.h"

PortComm::PortComm(const char* host, int portno, PortCommType type){
  valid = false;
  if(type == PortComm::SERVER){
    //printf("Server.\n");
    if(initHost(portno) > -1){
      valid = true;
      comm_type = type;
    }
  }else if(type == PortComm::CLIENT){
    //printf("Client.\n");
    if(initClie(host, portno) > -1){
      valid = true;
      comm_type = type;
    }
  }else{
    fprintf(stderr, "Unable to read port comm type.\n");
  }
}

PortComm::~PortComm(void){
  if(valid){
    close(sockfd);
    if(comm_type == PortComm::SERVER){
      close(cliefd);
    }  
  }
}

int PortComm::initClie(const char* host, int portno){

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0){
    fprintf(stderr, "Unable to open socket.\n");
    return -1;
  }

  //Perform host lookup
  server = gethostbyname(host);
  if (server == NULL){
    fprintf(stderr, "Unable to perform host lookup.\n");
    return -1;
  }

  //Setup the serv_addr structure
  bzero((char*) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  bcopy((char*) server->h_addr,
        (char*) &serv_addr.sin_addr.s_addr,
        server->h_length);
  serv_addr.sin_port = htons(portno);

  //Attempt to connect
  while (connect(sockfd, (struct sockaddr *)&serv_addr,
              sizeof(serv_addr)) < 0){
    fprintf(stderr, "Unable to connect to given host and port.\n");
    fprintf(stderr, "Host: %s | Port: %d\n", host, portno);
    fprintf(stderr, "Attempting reconnect in 5 second.\n");
    sleep(5);
  }

  //Connection sucessful
  return 0;
}

int PortComm::initHost(int portno){
  sockfd = socket(AF_INET, SOCK_STREAM, 0);

  if (sockfd < 0){
    fprintf(stderr, "Unable to open socket!\n");
    return -1;
  }

  //Zero buffer
  bzero((char*) &serv_addr, sizeof(serv_addr));

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(portno);
  serv_addr.sin_addr.s_addr = INADDR_ANY;

  // Allow reusability
  int yes = 1;
  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

  //Time to Bind
  if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0){
    fprintf(stderr, "Unable to bind socket.\n");
    return -1;
  }

  //Now time to listen for clients
  listen(sockfd, 5); //5 is number of waiting line
  
  //The system will hang on the following line until it gets a client
  clilen = sizeof(cli_addr);
  cliefd = accept(sockfd,
                     (struct sockaddr *) &cli_addr,
                     &clilen);

  if(cliefd < 0){
    fprintf(stderr, "Error on accepting new client.\n");
    return -1;
  }

  //Succesful init
  return 0;
}

bool PortComm::isValid(void){
  return valid;
}

int PortComm::write_port(unsigned char* buffer, int length){

  int write_sock = 0;
  if(comm_type == PortComm::CLIENT){
    write_sock = sockfd;
  }else if(comm_type == PortComm::SERVER){
    write_sock = cliefd;
  }else{
    printf("Unable to switch on comm type.\n");
  }

  if(!valid){
    printf("Socket is not valid.\n");
    return FAILURE_PORT;
  }

  // First send size of incoming buffer
  // Add one for the null terminator
  //  int size = (strlen(buffer) + 1) * sizeof(unsigned char);
  int size = length * sizeof(unsigned char);
  int bytes_sent = 0;
  while(bytes_sent < (int) sizeof(int)){
    int increment = write(write_sock, ((unsigned char *) &size) + bytes_sent, sizeof(int) - bytes_sent);
    if(increment == -1){
      continue;
    }
    bytes_sent += increment;
  }

  // Next copy to temp buffer
  unsigned char* message = (unsigned char*)(malloc(size));
  for(int i = 0; i < length; ++i){
    message[i] = buffer[i];
  }

  // Send this message array
  bytes_sent = 0;
  while(bytes_sent < size){
    int increment = write(write_sock, message+bytes_sent,
			  size-bytes_sent);
    if(increment == -1){
      continue;
    }
    bytes_sent += increment;
  }

  delete []message;
  return SUCCESS_PORT;
}

int PortComm::read_port(unsigned char **buffer, int* length){

  int read_port = 0;
  if(comm_type == PortComm::CLIENT){
    read_port = sockfd;
  }else if(comm_type == PortComm::SERVER){
    read_port = cliefd;
  }else{
    printf("Unable to switch on comm type.\n");
    return FAILURE_PORT;
  }
  
  int size = 0;
  int bytes_read = 0;
  while(bytes_read < (int) sizeof(int)){
    int increment = read(read_port, ((unsigned char *) &size) + bytes_read, sizeof(int) - bytes_read);
    if(increment == -1){
      continue;
    }
    bytes_read += increment;
  }
  *length = size;

  bytes_read = 0;
  *buffer = new unsigned char[size];

  while(bytes_read < size){
    int increment = read(read_port, (*buffer)+bytes_read, size-bytes_read);
    if(increment == -1){
      fprintf(stderr, "Error reading from socket.\n");
      return FAILURE_PORT;
    }
    bytes_read += increment;
  }

  return SUCCESS_PORT;
}
#endif
